/*
SYNOPSIS:

       ping  [ -c count]  [ -i interval]  [ -s packetsize]  ip_destination


FUNCIONAMIENTO:

    Envia <count> paquetes a <ip_destination> 
          con tamano <packetsize> y 
          intervalo entre cada envio de <interval> segundos.

          Por defecto count es 5, interval es 2 seg. y packetsize 
          es el minimo exigido por ICMP.

*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <ctype.h>
#include "memAndSem.h"
#include "icmp.h"
#include "nivelRed.h"

#define PING_ERR -1					/*!< Codigo de ERROR de ping*/
#define PING_OK 0					/*!< Codigo de OK de ping*/

#define DEFAULT_COUNT 5				/*!< Numero de envios por defecto*/
#define DEFAULT_INTERVAL 2			/*!< Tiempo de espera por defecto*/
#define DEFAULT_PACKETSIZE 0		/*!< Tamanno por defecto del paquete*/

#define PING_ID 314					/*!< Identificacion del proceso de PING*/

typedef struct{
	struct timeval sendTime;		/*!< Tiempo de envio del ECHO REQUEST*/
	short received;
} ECHO_DATA;

typedef struct __pingData{
	int size;						/*!< Tamanno del array de datos de ECHO*/
	ECHO_DATA *echoArray;			/*!< Array de datos de ECHO REQUEST*/
} *PING_DATA;

typedef enum{COUNT, INTERVAL, PACKETSIZE, UNDEF} PARAMETER_TYPE;


PING_DATA pingData=NULL;										/*!< EdD de almacenamiento de datos de envio*/
short pingOperative=0;											/*!< Flag de sistema ping activo*/
int nReceived=0;												/*!< Numero de ECHO REPLY recibidos*/
int nSend=0;													/*!< Numero de ECHO REQUEST enviados*/

short sleeping=0;												/*!< Flag de proceso dormido*/
pthread_mutex_t sleeping_mutex = PTHREAD_MUTEX_INITIALIZER;		/*!< Semaforo de acceso al flag sleeping*/

#define SEC_USEC_CONV (1e6)													/*!< Factor de conversion SEG-USEG*/
#define MSEC_USEC_CONV (1e3)												/*!< Factor de conversion MSEG-USEC*/
#define GET_USEC(time) (((time).tv_sec)*SEC_USEC_CONV+(time).tv_usec)		/*!< Retorna los microsegundos de time*/
#define DIFF_TIME(tEnd, tStart) (GET_USEC((tEnd))-GET_USEC((tStart)))		/*!< Retorna la diferecia entre tEnd y tStart en microsegundos*/
#define USEC_TO_MSEC(time) (((double)(time))/MSEC_USEC_CONV)				/*!< Convierte microsegundos en milisegundos*/

#define PING_LATENCY (1e11)													/*!< Latencia de espera (En microsegundos) a la recepcion de ECHO REPLY*/


/**
* @brief Funcion de notificacion de ECHO REPLY desde ICMP.
*
* @param dirSrc Direccion IP del emisor de ECHO REPLY.
* @param seq Numero de secuencia del ECHO REPLY.
* @param size Tamanno del datagrama recibido.
* @param data Datagrama recibido.
* @param opt Opciones IP recibidas.
* @return 0 si correcto, -1 si error.
*/
int pingNotify(BYTE *dirSrc, unsigned int seq, unsigned int size, BYTE *data, op_ip *opt);

/**
* @brief Extrae los parametros por linea de comandos del programa ping y los vuelca en los
* parametros adecuados.
*
* @param argc Numero de argumentos recibidos
* @param argv Array de cadenas de caracteres con los parametros recibidos
* @param count Puntero a entero en el que se almacenara el numero de iteraciones a realizar.
* @param interval Puntero a entero en el que se almacenera el intervalo de tiempo a esperar entre envios ECHO REQUEST.
* @param packetsize Puntero a entero en el que se almacenara el tamano de los datos a enviar.
* @param ipDst Array de BYTES en el que se almacenara la direccion IP contra la que se lanzara ping.
* @return 0 si correcto, -1 si error.
*/
int getParameters(int argc, char **argv, int *count, int *interval, int *packetsize, BYTE *ipDst);

/**
* @brief Crea lo datos para el envio de ECHO REQUEST.
*
* Nota: Esta funcion alloca memoria, para liberala,
* invoquese a free.
*
* @param packetsize Tamanno de los datos a enviar.
* @return Datos creados.
*/
BYTE *createData(int packetsize);

/**
* @brief Crea la EdD soporte para gestionar los ECHO REQUEST.
*
* Nota: Esta funcion alloca memoria, para liberarla,
* invoquese a freePingData.
*
* @param nEntries Numero de entradas a crear.
* @return EdD creada si correcto, NULL si error.
*/
PING_DATA createPingData(int nEntries);

/**
* @brief Libera la EdD soporte de gestion de ECHO REQUEST.
*
* @param pData Puntero a la EdD.
*/
void freePingData(PING_DATA *pData);

/**
* @brief Inserta en pData los datos de envio correspondientes al
* ECHO REQUET seq-esimo.
*
* @param pData EdD sobre la que se volcaran los datos.
* @param seq Numero de secuencia a insertar.
**/
void insertPingData(PING_DATA pData, int seq);

/**
* @brief Extrae los datos de envio del seq-esimo ECHO REQUEST.
*
* @param pData EdD de almacenamiento de datos.
* @param seq Numero de secuencia a extraer.
* @return Puntero a datos de ECHO si correcto, NULL si error.
*/
ECHO_DATA *extractEchoDataPingData(PING_DATA pData, int seq);

/**
* @brief Extrae tipo y datos de un argumento.
*
* @param string Cadena indicadora del tipo de argumento.
* @param stringData Cadena de almacenamiento del dato argumento.
* @param paramData Puntero sobre el que se volcara la informacion extraida.
* @return Tipo de argumento procesado, UNDEF si irreconocible.
*/
PARAMETER_TYPE getType(char *string, char *stringData, int *paramData);

/**
* @brief Bloque al proceso hasta el final de recepcion. Suspende el proceso ping
* a la espera de recepcion de todos los ECHO REPLY, hasta, a lo sumo PING_LATENCY nanosegundos.
*
* @return 0 si se salio por recepcion completa, -1 si timeout.
*/
int sleepUntilEndReception();

  
int pingNotify(BYTE *dirSrc, unsigned int seq, unsigned int size, BYTE *data, op_ip *opt)
{
	ECHO_DATA *echoData=NULL;
	struct timeval curTime;
	
	if(!pingOperative){			/*Modulo ping no operativo*/
		printf("Rechazado Echo Rep desde %d.%d.%d.%d por modulo inoperativo\n", dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3]);
		return PING_ERR;
	}
	
	echoData=extractEchoDataPingData(pingData, seq);		/*Extraccion de datos del ECHO REQUEST*/
	if(data==NULL || echoData==NULL){
		printf("Rechazado Echo Rep desde %d.%d.%d.%d\n", dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3]);
		return PING_ERR;
	}
	
	if(echoData->received){		/*Numero de secuencia ya recibido*/
		printf("Recibidos %d bytes desde %d.%d.%d.%d -> Seq = %d REPETIDO!\n", size, dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3], seq);
		return PING_OK;
	}
			
	echoData->received=1;
	nReceived++;
	
	/*Notificacion de recepcion de ECHO REPLY*/
	gettimeofday(&curTime, NULL);
	printf("Recibidos %d bytes desde %d.%d.%d.%d -> Seq = %d - Retardo = %.3f mseg\n", size,
			dirSrc[0], dirSrc[1], dirSrc[2], dirSrc[3], seq, USEC_TO_MSEC(DIFF_TIME(curTime, echoData->sendTime)));
	
	/*Acceso a zona critica para control de suspension del proceso ping*/
	pthread_mutex_lock(&sleeping_mutex);
	{
		if(nReceived==nSend && sleeping){
			sleeping=0;
			wakeup(getpid());
		}
	}
	pthread_mutex_unlock(&sleeping_mutex);
	
	return PING_OK;
}

int main(int argc, char **argv)
{
	int count, interval, packetsize;
	BYTE ipDst[IP_ALEN];
	int i=0;
	short status=PING_OK;
	BYTE *data=NULL;
	
	/*Captura de parametros*/
	if(getParameters(argc, argv, &count, &interval, &packetsize, ipDst)!=PING_OK){
		printf("Error de parametros de entrada: ping [-c count] [-i interval] [-s packetsize] ip_destination\n");
		return PING_ERR;
	}
	
	/*Inicializacion del Nivel de Red*/
	if(nivelRed_inicializa()!=0){
		printf("Error de inicializacion del Nivel de Red\n");
		return PING_ERR;
	}
	
	pingData=createPingData(count);			/*Creacion de la EdD para el soporte de datos de envio de ECHO REQ*/
	nSend=count;
	
	pingOperative=1;						/*Nivel ping activado*/
	data=createData(packetsize);			/*Creacion de los datos para ECHO REQUEST*/
	
	for(i=0; i<count; i++){
		insertPingData(pingData, i);
		
		if(icmp_envia_echo(ipDst, PING_ID, i, pingNotify, packetsize, data, NULL)!=0){		/*Envio de ECHO REQ por ICMP*/
			printf("Error de envio del ping %d\n", i+1);
			free(data);
			freePingData(&pingData);
			nivelRed_finaliza();
			return PING_ERR;
		}
		
		sleep(interval);
	}
	
	free(data);								/*Liberacion de los datos a enviar en ECHO REQUEST*/

	status=sleepUntilEndReception();		/*Suspension del proceso hasta recepcion completa o timeout*/
	
	pingOperative=0;						/*Nivel ping desactivado*/
	
	/*Finalizacion del Nivel de Red*/
	if(nivelRed_finaliza()!=0){
		printf("Error al finalizar el Nivel de Red\n");
		status=PING_ERR;
	}
	
	freePingData(&pingData);				/*Liberacion de la EdD para el soporte de datos de envio de ECHO REQ*/
	
	printf("------------------------RESUMEN------------------------\n\tEnviados: %d - Recibidos: %d - Perdidos: %.2f %%\n"
			,count, nReceived, ((double)(count-nReceived))/(count)*100);
	
	return status;
}

int getParameters(int argc, char **argv, int *count, int *interval, int *packetsize, BYTE *ipDst)
{
	int i, paramData;
	PARAMETER_TYPE type;
	
	if(argc<2 || (argc%2)!=0){
		return PING_ERR;
	}
	
	if(lee_cadena_ip(argv[argc-1], ipDst)!=0){
		return PING_ERR;
	}
	
	*count=DEFAULT_COUNT;
	*interval=DEFAULT_INTERVAL;
	*packetsize=DEFAULT_PACKETSIZE;
	
	/*Parsing secuencial de argumentos: Los argumentos pares contienen el identificador
	de parametro, y los argumentos impares los datos del parametro*/
	for(i=1; i<argc-1; i+=2){
		type=getType(argv[i], argv[i+1], &paramData);		/*Extraccion de tipo y dato*/
		switch(type){
			case COUNT:
				*count=paramData;
				break;
			case INTERVAL:
				*interval=paramData;
				break;
			case PACKETSIZE:
				*packetsize=paramData;
				break;
			case UNDEF:
				return PING_ERR;
			default:
				return PING_ERR;
		}
	}
	
	return PING_OK;
}

BYTE *createData(int packetsize)
{
	BYTE *data=NULL;
	
	data=(BYTE *)malloc(packetsize*sizeof(BYTE));		/*Creacion de packetsize bytes de basura*/

	return data;
}

PING_DATA createPingData(int nEntries)
{
	PING_DATA pData=NULL;
	int i;
	
	/*Reserva de la EdD*/
	pData=(struct __pingData *)calloc(1, sizeof(struct __pingData));
	if(pData==NULL){
		return NULL;
	}
	
	pData->echoArray=(ECHO_DATA *)calloc(nEntries, sizeof(ECHO_DATA));
	if(pData->echoArray==NULL){
		free(pData);
		return NULL;
	}
	
	for(i=0; i<nEntries; i++){
		pData->echoArray[i].received=0;
	}
	
	pData->size=nEntries;
	
	return pData;
}


void freePingData(PING_DATA *pData)
{
	if(pData==NULL || *pData==NULL){
		return;
	}
	
	free((*pData)->echoArray);
	free(*pData);
	
	*pData=NULL;
}


void insertPingData(PING_DATA pData, int seq)
{
	if(pData==NULL || pData->echoArray==NULL || seq>pData->size){
		return;
	}

	gettimeofday(&(pData->echoArray[seq].sendTime), NULL);
	
	return;
}

ECHO_DATA *extractEchoDataPingData(PING_DATA pData, int seq)
{
	if(pData==NULL || pData->echoArray==NULL || seq>pData->size){
		return NULL;
	}

	return &(pData->echoArray[seq]);
}

PARAMETER_TYPE getType(char *string, char *stringData, int *paramData)
{
	PARAMETER_TYPE type=UNDEF;
	
	if(string==NULL || stringData==NULL){
		return UNDEF;
	}
	
	if(string[0]!='-'){			/*Cadena inesperada en posicion de identificador de argumento*/
		return UNDEF;
	}
	
	switch(string[1]){
		case 'c':
			type=COUNT;
			break;
			
		case 'i':
			type=INTERVAL;
			break;
		
		case 's':
			type=PACKETSIZE;
			break;
			
		default:
			return UNDEF;
	}
	
	if(isdigit(stringData[0])==0){		/*Cadena inesperada en posicion de argumento*/
		return UNDEF;
	}
	
	*paramData=atoi(stringData);
	
	return type;
}

int sleepUntilEndReception()
{
	int status;
	short privateSleeping;
	
	/*Entrada a zona critica*/
	pthread_mutex_lock(&sleeping_mutex);
	{
		if(nReceived==nSend){			/*Recibida la respuesta a todos los ECHO REQUEST -> No iniciar espera*/
			status=PING_OK;
		}
		else{							/*No recibidas todas las respuestas -> Iniciar espera*/
			sleeping=1;	
			privateSleeping=1;			
		}
	}
	pthread_mutex_unlock(&sleeping_mutex);
	/*Salida de zona critica*/
	
	if(privateSleeping){
		if(sleepUntilWakeup(PING_LATENCY)==MEM_AND_SEM_TIMEOUT){
			printf("Agotado el tiempo de espera por respuesta\n");
			status=PING_ERR;
		}
	}
	return status;
}

